package p.ithorns.framework.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import p.ithorns.framework.common.model.Asset;
import p.ithorns.framework.core.constants.FileConsts;
import p.ithorns.framework.core.model.ZipTreeNode;
import p.ithorns.framework.core.model.ZipUploadResp;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * MapLayerUtil
 * <p>
 * 地图图层工具：对分层地图zip解压，然后按层次存储
 *
 * @author HtL
 * @date 2024/6/20 8:31
 * @since 1.0.0
 */
public class MapLayerUtil {

    private static final Logger log = LoggerFactory.getLogger(MapLayerUtil.class);

    // private AssetsConfig assets;


    public ZipUploadResp uploadZip(MultipartFile file) {
        // 校验文件
        verifyZip(file);

        Asset asset = null;
        try {
            // 初始化
            asset = initAsset(file);

            // 存zip
            storeMapZip(asset, file);

            // 解压zip并保存瓦片文件
            unzipAndStoreTiles(asset);

            // return
            URI baseUri = asset.getBaseUri();
            return ZipUploadResp.builder()
                    .name(asset.getName())
                    .size(file.getSize())
                    .zipUri(baseUri + FileConsts.PATH_SYMBOL + asset.getName())
                    .tilesUri(baseUri + FileConsts.PATH_SYMBOL + "tiles")
                    .build();
        } catch (Exception e) {
            log.error(e.getMessage());
            // 异常后删除创建的资源
            if (null != asset) {
                FileUtil.delete(asset.getAbsPath());
            }
            throw new RuntimeException("存储Map文件失败：" + e.getMessage());
        }
    }

    /**
     * 校验zip文件
     */
    private void verifyZip(MultipartFile file) {
        String contentType = file.getContentType();
        if (!Objects.requireNonNull(contentType).contains(FileConsts.FILE_TYPE_ZIP)) {
            throw new RuntimeException("文件校验失败：请上传正确的zip类型文件");
        }
    }

    /**
     * 初始化Asset
     */
    private Asset initAsset(MultipartFile file) throws IOException {
        String fileName = file.getOriginalFilename();
        if (StringUtil.isEmpty(fileName)) {
            throw new RuntimeException("文件名不能为空");
        }

        // 用新文件名在map-layer下创建一个文件夹，每个资源一个文件夹
        String newFileRoot = FileUtil.newName(fileName);
        // TODO 这里newFileRoot 需要加上基础路径
        Path directory = Paths.get(newFileRoot);
        if (Files.notExists(directory)) {
            Files.createDirectory(directory);
        }

        // 加上文件后缀
        String suffix = StringUtil.getFileSuffix(fileName);
        String fileNameWithSuffix = newFileRoot + suffix;

        // 构建回显相对地址
        String baseUri = ""; // asset.getBaseUri()
        String base = baseUri + FileConsts.PATH_SYMBOL + newFileRoot;
        Asset target = new Asset(fileNameWithSuffix, base);
        log.info("初始化上传资源: {}", target);
        return target;
    }

    /**
     * 存储zip文件
     * 1. 新建文件名
     * 2. 以这个文件名为文件夹名，后面将zip文件和tiles存储到这个文件夹下
     *
     * @param asset -
     * @param file  -
     */
    private void storeMapZip(Asset asset, MultipartFile file) throws IOException {
        // 存储zip文件
        String zipFile = FileUtil.store(file.getInputStream(), asset.getFileAbsPath());
        log.info("存储Zip文件： {}", zipFile);
    }

    private void unzipAndStoreTiles(Asset asset) {
        // 任务计数
        LongAdder count = new LongAdder();
        CompletableFuture<String> messenger = new CompletableFuture<>();
        Queue<CompletableFuture<String>> futures = new ConcurrentLinkedQueue<>();

        // 创建tiles文件夹
        String tilesDir = asset.getAbsPath() + File.separator + "tiles";
        Path tilesPath = Paths.get(tilesDir);
        FileUtil.createDirIfAbsent(tilesPath);

        List<ZipTreeNode> availableNode;
        try (ZipFile zipFile = new ZipFile(asset.getFileAbsPath())) {
            ZipTreeNode sourceNode = ZipUtil.buildZipTree(zipFile);
            availableNode = getAvailableNodes(sourceNode);

            // 处理内部清单
            for (ZipTreeNode node : availableNode) {
                ZipUtil.traverseZipTree(node, tilesPath, subNode -> {
                    // 目录
                    if (subNode.isDirectory()) {
                        FileUtil.createDirIfAbsent(subNode.getFullPath());
                        return;
                    }
                    // 文件
                    CompletableFuture<String> future = saveNode(zipFile, subNode, messenger);
                    if (null != future) {
                        count.add(1);
                        futures.add(future);
                    }
                });
            }

            // 异常处理
            messenger.exceptionally(e -> {
                log.error("异步存储文件执行失败.", e);
                handleFileSaveError(futures, tilesPath);
                return null;
            });

            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            log.info("解压Zip[{}], 并存储[{}]个Tile文件", asset.getName(), count.sum());
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    private CompletableFuture<String> saveNode(ZipFile zipFile, ZipTreeNode subNode,
                                               CompletableFuture<String> messenger) {
        String name = subNode.getName();
        ZipEntry entry = subNode.getEntry();
        Path fullPath = subNode.getFullPath();
        return CompletableFuture.supplyAsync(() -> {
                    try {
                        InputStream inputStream = zipFile.getInputStream(entry);
                        byte[] bytes = ByteUtil.copyBytes(inputStream);
                        FileUtil.store(bytes, fullPath);
                        // log.info("存储文件：{}", fullPath);
                        return name;
                    } catch (IOException e) {
                        log.warn("tile文件保存失败：{}, {}", name, e.getMessage());
                        throw new RuntimeException(e);
                    }
                }, Executors.newCachedThreadPool())
                .exceptionally(e -> {
                    messenger.completeExceptionally(new RuntimeException(e));
                    return name;
                });
    }

    private void handleFileSaveError(Queue<CompletableFuture<String>> futures,
                                     Path tilesPath) {
        // 取消所有未完成的任务
        for (CompletableFuture<String> future : futures) {
            if (future.isDone()) {
                future.cancel(true);
            }
        }

        // 清理已存储的文件
        FileUtil.delete(tilesPath);
    }


    /**
     * 获取有效的文件节点
     * 这里就是控制目录层级的地方
     *
     * @param root -zip根节点(包含自己)
     * @return List<ZipTreeNode>
     */
    private static List<ZipTreeNode> getAvailableNodes(ZipTreeNode root) {
        List<ZipTreeNode> childrenOfRoot = root.getChildren();
        if (null == childrenOfRoot) {
            return Collections.emptyList();
        }

        // 如果只有1个文件夹，说明是连目录一起压缩的，则需要处理这个文件夹下的所有文件
        if (childrenOfRoot.size() == 1) {
            ZipTreeNode firstSubNode = childrenOfRoot.get(0);
            if (null != firstSubNode) {
                if (firstSubNode.isDirectory()) {
                    List<ZipTreeNode> childrenOfFirstSubNode = firstSubNode.getChildren();
                    if (null != childrenOfFirstSubNode) {
                        return childrenOfFirstSubNode;
                    }
                    return Collections.emptyList();
                }

                // 返回自己
                return Collections.singletonList(firstSubNode);
            }
            return Collections.emptyList();
        }

        // 如果不止1个，说明所有文件就在根目录下，当然就直接处理根节点下的所有文件
        return childrenOfRoot;
    }

}